﻿/**************************************************************************
创建日期:	2019/6/10
作	  者:	张存
邮 	  箱:	zhangcunliang@126.com
创建时间:	
Copyright (c)  ZhCun.cn

描	述：  几种GUID的生成
记	录：  增加自定义Id的生成 NewId() 2019.8.21
***************************************************************************/

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace ASmile.Helpers
{
    enum SequentialGuidType
    {
        /// <summary>
        /// mysql 等字符串
        /// </summary>
        SequentialAsString,
        /// <summary>
        /// oracal 二进制
        /// </summary>
        SequentialAsBinary,
        /// <summary>
        /// sql server 
        /// </summary>
        SequentialAtEnd
    }

    /// <summary>
    /// 有序guid的生成
    /// </summary>
    public class GuidHelper
    {
        /// <summary>
        /// 用于计算时间戳的开始时间
        /// </summary>
        private static readonly DateTime DT_Start = new DateTime(2000, 1, 1, 0, 0, 0, 0);
        private static readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();

        private static long GetTimestamp()
        {
            var r = (DateTime.UtcNow.Ticks - DT_Start.Ticks) / 10000;
            return r;
        }
        /// <summary>
        /// 指定类型生成有序GUID，6字节时间戳与 10字节原GUID
        /// </summary>
        /// <param name="guidType">生成类型</param>
        /// <returns>返回GUID</returns>
        private static Guid NewSequentialGuid(SequentialGuidType guidType)
        {
            byte[] randomBytes = new byte[10];
            _rng.GetBytes(randomBytes);
            long timestamp = GetTimestamp();
            byte[] timestampBytes = BitConverter.GetBytes(timestamp);
            if (BitConverter.IsLittleEndian)
            {
                Array.Reverse(timestampBytes);
            }
            byte[] guidBytes = new byte[16];
            switch (guidType)
            {
                case SequentialGuidType.SequentialAsString:
                case SequentialGuidType.SequentialAsBinary:
                    Buffer.BlockCopy(timestampBytes, 2, guidBytes, 0, 6);
                    Buffer.BlockCopy(randomBytes, 0, guidBytes, 6, 10);

                    // If formatting as a string, we have to reverse the order
                    // of the Data1 and Data2 blocks on little-endian systems.
                    if (guidType == SequentialGuidType.SequentialAsString && BitConverter.IsLittleEndian)
                    {
                        Array.Reverse(guidBytes, 0, 4);
                        Array.Reverse(guidBytes, 4, 2);
                    }
                    break;

                case SequentialGuidType.SequentialAtEnd:
                    Buffer.BlockCopy(randomBytes, 0, guidBytes, 0, 10);
                    Buffer.BlockCopy(timestampBytes, 2, guidBytes, 10, 6);
                    break;
            }
            return new Guid(guidBytes);
        }

        /// <summary>
        /// 生成Sqlserver用的Guid格式
        /// </summary>
        /// <returns></returns>
        public static Guid NewSqlId()
        {
            return NewSequentialGuid(SequentialGuidType.SequentialAtEnd);
        }
        /// <summary>
        /// 生成按字符串顺序用的Guid格式(mysql)
        /// </summary>
        public static Guid NewStrId()
        {
            return NewSequentialGuid(SequentialGuidType.SequentialAsString);
        }
        /// <summary>
        /// 生成二进制顺序的Guid格式(oracle)
        /// </summary>
        /// <returns></returns>
        public static Guid NewBinId()
        {
            return NewSequentialGuid(SequentialGuidType.SequentialAsBinary);
        }
        /// <summary>
        /// utc时间开始
        /// </summary>
        readonly static DateTime UTC_START = new DateTime(1970, 1, 1, 0, 0, 0, 0);
        /// <summary>
        /// 生成有时间戳+随机数组成的22位字符串（最后两位标识生成方式，SqlServer：00，本程序：01）
        /// </summary>
        public static string NewId()
        {
            StringBuilder sb = new StringBuilder();
            long ts = (long)(DateTime.UtcNow - UTC_START).TotalMilliseconds;
            var tsByts = BitConverter.GetBytes(ts);
            for (int i = 5; i >= 0; i--)
            {
                sb.Append(tsByts[i].ToString("X2"));
            }
            byte[] randomBytes = new byte[4];
            _rng.GetBytes(randomBytes);
            for (int i = 0; i < randomBytes.Length; i++)
            {
                sb.Append(randomBytes[i].ToString("X2"));
            }
            sb.Append("01");
            return sb.ToString();
        }
        /// <summary>
        /// 生成guid的Base64格式
        /// </summary>
        public static string NewGuid()
        {
            var bs = Guid.NewGuid().ToByteArray();
            return Convert.ToBase64String(bs);            
        }
    }
}